package edu.vcu.csl.ess;

import java.awt.*;
import java.util.*;
import javax.swing.*;
import javax.swing.event.*;
import java.awt.event.*;
import javax.swing.border.*;



import java.io.*;


class GUI extends JFrame {
	class BasicGridBagPanel extends JPanel {
		GridBagConstraints gbc = new GridBagConstraints();

		public BasicGridBagPanel(int anchor, int fill, Insets insets) {
			setLayout(new GridBagLayout());
			gbc.anchor = anchor;
			gbc.fill = fill;
			gbc.insets = insets;
		}

		public void add(Component c,
				int gridx, int gridy, 
				int weightx, int weighty) {
			gbc.gridx = gridx;
			gbc.gridy = gridy;
			gbc.weightx = weightx;
			gbc.weighty = weighty;
			add(c,gbc);
		}

		public void setGridWidth(int value) {
			gbc.gridwidth = value;
		}
	}

	class BasicTextArea extends JTextArea {
		public BasicTextArea(int fontType, float fontSize) {
			Font font = getFont();
			font = font.deriveFont(fontType, fontSize);
			this.setFont(font);
			this.setLineWrap(true);
			this.setWrapStyleWord(true);
			this.setMargin(new Insets(2,2,2,2));

			Set<KeyStroke> strokes = 
				new HashSet<KeyStroke>(Arrays.asList(KeyStroke.getKeyStroke("pressed TAB")));
			setFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, strokes);
			strokes = 
				new HashSet<KeyStroke>(Arrays.asList(KeyStroke.getKeyStroke("shift pressed TAB")));
			setFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS, strokes);	    
		}

		public Dimension getPreferredSize() {
			Dimension d = super.getPreferredSize();
			d.setSize(0,d.getHeight());
			return d;
		}
	}

	class BasicLabel extends BasicTextArea {
		public BasicLabel(int fontType, 
				float fontSize, 
				String text) {
			super(fontType, fontSize);
			setText(text);
			setEditable(false);
			setFocusable(false);
		}
	}

	class BasicIconButton extends JButton {
		public BasicIconButton(String iconfilename, 
				String actionCommand, 
				ActionListener listener) {
			super(new ImageIcon(iconfilename));
			setActionCommand(actionCommand);
			addActionListener(listener);
			setMargin(new Insets(0,0,0,0));
			setFocusable(false);
		}
	}

	class BasicInputForm extends BasicGridBagPanel {
		String        itemNames[];
		BasicLabel    itemLabels[];
		BasicTextArea itemValues[];

		public BasicInputForm(String itemNames[], DocumentListener listener) {
			super(GridBagConstraints.NORTHEAST,
					GridBagConstraints.BOTH,
					new Insets(1,1,1,1));
			setBackground(Color.black);
			this.itemNames = itemNames;
			itemLabels = new BasicLabel[itemNames.length];
			itemValues = new BasicTextArea[itemNames.length];
			for(int i=0;i<itemNames.length;i++) {
				itemLabels[i] = new BasicLabel(Font.BOLD,12.0f,itemNames[i]);
				itemLabels[i].setBackground(new Color(200,200,200));
				add(itemLabels[i],0,i,1,1);
				itemValues[i] = new BasicTextArea(Font.BOLD,12.0f);
				add(itemValues[i],1,i,4,1);
				itemValues[i].getDocument().addDocumentListener(listener);
			}
		}

		public void setText(String name, String value) {
			for(int i=0; i<itemNames.length; i++) {
				if (itemNames[i].equals(name)) {
					itemValues[i].setText(value);
				}
			}
		}

		public String getText(String name) {
			for(int i=0; i<itemNames.length; i++) {
				if (itemNames[i].equals(name)) {
					return itemValues[i].getText();
				}
			}
			System.out.println("getText on: " + name + " failed.");
			System.exit(1);
			return null;
		}
	}

	class HeaderElement extends BasicGridBagPanel {
		BasicLabel      label;
		BasicIconButton addButton;

		public HeaderElement(String name, ActionListener actionListener) {
			super(GridBagConstraints.EAST,
					GridBagConstraints.HORIZONTAL,
					new Insets(3,3,3,3));
			label = new BasicLabel(Font.BOLD,16.0f,name);
			label.setOpaque(false);
			label.setForeground(Color.white);
			setBackground(Color.blue);

			addButton = new BasicIconButton("icons/new_untitled_text_file.gif","add", actionListener);

			add(label,0,0,1,0);
			add(addButton,1,0,0,0);
		}
	}

	abstract class DisplayableEntity {
		abstract public String[] getHeader();
		abstract public void updateLabel(BasicInputForm form, BasicLabel label);
		abstract public void getDataFromGUI(BasicInputForm form, GUIData data);
		abstract public void loadDataIntoGUI(BasicInputForm form, GUIData data, int index);
	}      

	class ParameterEntity extends DisplayableEntity {
		public String[] getHeader() {
			String strs[] = {"Name","Value","Comment"};
			return strs;
		}

		public void updateLabel(BasicInputForm form, BasicLabel label) {
			String str = form.getText("Name") + " = " + form.getText("Value");
			if (!form.getText("Comment").equals("")) {
				str += "\nCOMMENT: " + form.getText("Comment");
			}
			label.setText(str);
		}

		public void getDataFromGUI(BasicInputForm form, GUIData data) {
			data.addParameter(form.getText("Name"),
					form.getText("Value"),
					form.getText("Comment"));
		}

		public void loadDataIntoGUI(BasicInputForm form, GUIData data, int index) {
			GUIData.Parameter x = data.parameters.get(index);
			form.setText("Name",x.name);
			form.setText("Value",x.value);
			form.setText("Comment",x.comment);
		}
	}

	class SpeciesEntity extends DisplayableEntity {
		public String[] getHeader() {
			String strs[] = {"Name","Population","Comment"};
			return strs;
		}

		public void updateLabel(BasicInputForm form, BasicLabel label) {
			String str = form.getText("Name") + " population=" + form.getText("Population");
			if (!form.getText("Comment").equals("")) {
				str += "\nCOMMENT: " + form.getText("Comment");
			}
			label.setText(str);
		}

		public void getDataFromGUI(BasicInputForm form, GUIData data) {
			data.addSpecies(form.getText("Name"),
					form.getText("Population"),
					form.getText("Comment"));
		}

		public void loadDataIntoGUI(BasicInputForm form, GUIData data, int index) {
			GUIData.Species x = data.species.get(index);
			form.setText("Name",x.name);
			form.setText("Population",x.population);
			form.setText("Comment",x.comment);
		}
	}

	class ReactionEntity extends DisplayableEntity {
		public String[] getHeader() {
			String strs[] = {"Name","Equation","Rate","Comment"};
			return strs;
		}

		public void updateLabel(BasicInputForm form, BasicLabel label) {
			String str = form.getText("Name") 
			+ " equation=" + form.getText("Equation") 
			+ " rate=" + form.getText("Rate");
			if (!form.getText("Comment").equals("")) {
				str += "\nCOMMENT: " + form.getText("Comment");
			}
			label.setText(str);
		}

		public void getDataFromGUI(BasicInputForm form, GUIData data) {
			data.addReaction(form.getText("Name"),
					form.getText("Equation"),
					form.getText("Rate"),
					form.getText("Comment"));
		}

		public void loadDataIntoGUI(BasicInputForm form, GUIData data, int index) {
			GUIData.Reaction x = data.reactions.get(index);
			form.setText("Name",x.name);
			form.setText("Equation",x.equation);
			form.setText("Rate",x.rate);
			form.setText("Comment",x.comment);
		}
	}


	class EventEntity extends DisplayableEntity {
		public String[] getHeader() {
			String strs[] = {"Name","Time","Action","Comment"};
			return strs;
		}

		public void updateLabel(BasicInputForm form, BasicLabel label) {
			String str = form.getText("Name") 
			+ " time=" + form.getText("Time") + 
			"\nAction:\n" + form.getText("Action");
			if (!form.getText("Comment").equals("")) {
				str += "\nCOMMENT: " + form.getText("Comment");
			}
			label.setText(str);
		}

		public void getDataFromGUI(BasicInputForm form, GUIData data) {
			data.addEvent(form.getText("Name"),
					form.getText("Time"),
					form.getText("Action"),
					form.getText("Comment"));
		}

		public void loadDataIntoGUI(BasicInputForm form, GUIData data, int index) {
			GUIData.Event x = data.events.get(index);
			form.setText("Name",x.name);
			form.setText("Time",x.time);
			form.setText("Action",x.action);
			form.setText("Comment",x.comment);
		}
	}


	class PeriodicEventEntity extends DisplayableEntity {
		public String[] getHeader() {
			String strs[] = {"Name","Start Time","Period","End Time","Action","Comment"};
			return strs;
		}

		public void updateLabel(BasicInputForm form, BasicLabel label) {
			String str = form.getText("Name") 
			+ " start=" + form.getText("Start Time") 
			+ " period=" + form.getText("Period")  
			+ " end=" + form.getText("End Time")  
			+ "\nAction:\n" + form.getText("Action");
			if (!form.getText("Comment").equals("")) {
				str += "\nCOMMENT: " + form.getText("Comment");
			}
			label.setText(str);
		}

		public void getDataFromGUI(BasicInputForm form, GUIData data) {
			data.addPeriodic(form.getText("Name"),
					form.getText("Start Time"),
					form.getText("Period"),
					form.getText("End Time"),
					form.getText("Action"),
					form.getText("Comment"));
		}

		public void loadDataIntoGUI(BasicInputForm form, GUIData data, int index) {
			GUIData.Periodic x = data.periodics.get(index);
			form.setText("Name",x.name);
			form.setText("Start Time",x.start);
			form.setText("Period",x.period);
			form.setText("End Time",x.end);
			form.setText("Action",x.action);
			form.setText("Comment",x.comment);
		}
	}

	class TriggerEntity extends DisplayableEntity {
		public String[] getHeader() {
			String strs[] = {"Name","Condition","Action","Comment"};
			return strs;
		}

		public void updateLabel(BasicInputForm form, BasicLabel label) {
			String str = form.getText("Name") +
			"\nCondition:\n" + form.getText("Condition") +
			"\nAction:\n" + form.getText("Action");
			if (!form.getText("Comment").equals("")) {
				str += "\nCOMMENT: " + form.getText("Comment");
			}
			label.setText(str);
		}

		public void getDataFromGUI(BasicInputForm form, GUIData data) {
			data.addTrigger(form.getText("Name"),
					form.getText("Condition"),
					form.getText("Action"),
					form.getText("Comment"));
		}

		public void loadDataIntoGUI(BasicInputForm form, GUIData data, int index) {
			GUIData.Trigger x = data.triggers.get(index);
			form.setText("Name",x.name);
			form.setText("Condition",x.condition);
			form.setText("Action",x.action);
			form.setText("Comment",x.comment);
		}
	}

	class SimulationEntity extends DisplayableEntity {
		public String[] getHeader() {
			String strs[] = {"Name","Start Time","Print Interval","End Time","Seed","Output Filename"};
			return strs;
		}

		public void updateLabel(BasicInputForm form, BasicLabel label) {
			String str = form.getText("Name") +
			" start=" + form.getText("Start Time") +
			" interval=" + form.getText("Print Interval") +
			" end=" + form.getText("End Time") +
			" seed=" + form.getText("Seed");
			label.setText(str);
		}

		public void getDataFromGUI(BasicInputForm form, GUIData data) {
			data.addSimulation(form.getText("Name"),
					form.getText("Start Time"),
					form.getText("Print Interval"),
					form.getText("End Time"),
					form.getText("Seed"),
					form.getText("Output Filename"));
		}

		public void loadDataIntoGUI(BasicInputForm form, GUIData data, int index) {
			GUIData.Simulation x = data.simulations.get(index);
			form.setText("Name",x.name);
			form.setText("Start Time",x.start);
			form.setText("Print Interval",x.interval);
			form.setText("End Time",x.end);
			form.setText("Seed",x.seed);
			form.setText("Output Filename",x.outputfilename);
		}
	}

	class BasicElement extends BasicGridBagPanel implements DocumentListener {
		BasicIconButton expand;
		BasicIconButton copy;  
		BasicIconButton delete;
		ActionListener  actionListener;
		boolean         expanded = true;	
		BasicInputForm  inputForm;
		BasicLabel      label;
		DisplayableEntity entity;

		public BasicElement(DisplayableEntity entity, ActionListener listener) {
			super(GridBagConstraints.EAST,
					GridBagConstraints.HORIZONTAL,
					new Insets(3,3,3,3));

			this.entity = entity;
			this.actionListener = listener;
			setBackground(Color.white);
			inputForm = new BasicInputForm(entity.getHeader(),this);
			expand = new BasicIconButton("icons/collapseall.gif","expand",listener);
			copy = new BasicIconButton("icons/copy_edit.gif","copy",listener);
			delete = new BasicIconButton("icons/delete_template.gif","delete",listener);
			label = new BasicLabel(Font.BOLD,14.0f,"");
			update();
		}

		public BasicElement copy() {
			BasicElement element = new BasicElement(entity,actionListener);
			String header[] = entity.getHeader();
			for(int i=0; i<header.length; i++) 
				element.inputForm.setText(header[i],inputForm.getText(header[i]));
			return element;
		}

		public void update() {
			removeAll();
			entity.updateLabel(inputForm,label);
			setGridWidth(1);
			add(expand,0,0,0,0);
			add(label,1,0,1,0);
			add(copy,2,0,0,0);
			add(delete,3,0,0,0);
			if (expanded)
				add(inputForm,1,2,1,0);
			revalidate();
		}

		public void toggleExpand() {
			expanded = !expanded;
			if (expanded) {
				expand.setIcon(new ImageIcon("icons/collapseall.gif"));		
			} else {
				expand.setIcon(new ImageIcon("icons/expandall.gif"));
			} 
			update();
		}

		public void changedUpdate(DocumentEvent e) {
			entity.updateLabel(inputForm,label);
		}

		public void insertUpdate(DocumentEvent e) {
			entity.updateLabel(inputForm,label);
		}

		public void removeUpdate(DocumentEvent e) {
			entity.updateLabel(inputForm,label);
		}

		public void getDataFromGUI(GUIData data) {
			entity.getDataFromGUI(inputForm,data);
		}

		public void loadDataIntoGUI(GUIData data, int index) {
			entity.loadDataIntoGUI(inputForm,data,index);
		}
	}

	class BasicElementList extends BasicGridBagPanel implements ActionListener{
		HeaderElement header;
		Vector<BasicElement> elements = new Vector<BasicElement>();
		DisplayableEntity entity;

		public BasicElementList(String header, DisplayableEntity entity) {
			super(GridBagConstraints.EAST,
					GridBagConstraints.HORIZONTAL,
					new Insets(1,1,1,1));
			this.header = new HeaderElement(header,this);
			this.entity = entity;
			setBackground(Color.black);
			update();
		}

		public void update() {
			removeAll();
			int rows = 0;
			add(header,0,rows++,1,0);
			for(int i=0; i<elements.size(); i++) {
				add(elements.get(i),0,rows++,1,0);
			}
			repaint();
			revalidate();
		}

		public void actionPerformed(ActionEvent e) {
			if ("add".equals(e.getActionCommand())) {
				elements.add(new BasicElement(entity,this));
			} else if ("delete".equals(e.getActionCommand())) {
				elements.remove(((BasicIconButton)(e.getSource())).getParent());
			} else if ("copy".equals(e.getActionCommand())) {
				BasicElement element = (BasicElement)
				((BasicIconButton)(e.getSource())).getParent();
				elements.add(element.copy());		
			} else if ("expand".equals(e.getActionCommand())) {
				BasicElement element = (BasicElement)
				((BasicIconButton)(e.getSource())).getParent();
				element.toggleExpand();
			}
			update();
		}

		public void getDataFromGUI(GUIData data) {
			for(int i=0; i<elements.size(); i++) {
				elements.get(i).getDataFromGUI(data);
			}
		}

		public void clear() {
			elements.clear();
			update();
		}

		public void loadDataIntoGUI(GUIData data, int size) {
			for(int i=0; i<size; i++) {
				elements.add(new BasicElement(entity,this));
				elements.get(i).loadDataIntoGUI(data,i);
				elements.get(i).toggleExpand();
			}
			update();
		}
	}

	class MainPanel extends BasicGridBagPanel {
		BasicElementList parameters  = new BasicElementList("Parameters",new ParameterEntity());
		BasicElementList species     = new BasicElementList("Species",new SpeciesEntity());
		BasicElementList reactions   = new BasicElementList("Reactions",new ReactionEntity());
		BasicElementList events      = new BasicElementList("Events",new EventEntity());
		BasicElementList periodics   = new BasicElementList("Periodic Events",new PeriodicEventEntity());
		BasicElementList triggers    = new BasicElementList("Triggers",new TriggerEntity());
		BasicElementList simulations = new BasicElementList("Simulations",new SimulationEntity());

		public MainPanel() {
			super(GridBagConstraints.NORTHEAST,
					GridBagConstraints.BOTH,
					new Insets(5,5,5,5));
			setBackground(Color.white);
			add(parameters  ,0,0,1,0);
			add(species     ,0,1,1,0);
			add(reactions   ,0,2,1,0);
			add(events      ,0,3,1,0);
			add(periodics   ,0,4,1,0);
			add(triggers    ,0,5,1,0);
			add(simulations ,0,6,1,0);
			JPanel filler = new JPanel();
			filler.setBackground(Color.white);
			add(filler,0,7,1,1);
		}

		public void getDataFromGUI(GUIData data) {
			parameters.getDataFromGUI(data);
			species.getDataFromGUI(data);
			reactions.getDataFromGUI(data);
			events.getDataFromGUI(data);
			periodics.getDataFromGUI(data);
			triggers.getDataFromGUI(data);
			simulations.getDataFromGUI(data);
		}

		public void clear() {
			parameters.clear();
			species.clear();
			reactions.clear();
			events.clear();
			periodics.clear();
			triggers.clear();
			simulations.clear();
		}

		public void loadDataIntoGUI(GUIData data) {
			parameters.loadDataIntoGUI(data, data.parameters.size());
			species.loadDataIntoGUI(data, data.species.size());
			reactions.loadDataIntoGUI(data, data.reactions.size());
			events.loadDataIntoGUI(data, data.events.size());
			periodics.loadDataIntoGUI(data, data.periodics.size());
			triggers.loadDataIntoGUI(data, data.triggers.size());
			simulations.loadDataIntoGUI(data, data.simulations.size());
		}
	}

	MainPanel mainPanel = new MainPanel();
	final JFileChooser fileChooser = new JFileChooser();

	public GUI() {
		setContentPane(new JScrollPane(mainPanel));
		setSize(640,800);
		initMenu();
		setTitle("VCU ESS v0.1");
		setVisible(true);
	}

	public void initMenu() {
		JMenuBar menuBar = new JMenuBar();

		JMenu mFile = new JMenu("File");
		menuBar.add(mFile);

		JMenuItem miNew = new JMenuItem("New");
		JMenuItem miOpen = new JMenuItem("Open");
		JMenuItem miSave = new JMenuItem("Save");
		JMenuItem miRun = new JMenuItem("Run");
		JMenuItem miExit = new JMenuItem("Exit");
		mFile.add(miNew);
		mFile.addSeparator();
		mFile.add(miOpen);
		mFile.add(miSave);
		mFile.addSeparator();
		mFile.add(miRun);
		mFile.addSeparator();
		mFile.add(miExit);

		JMenu mHelp = new JMenu("Help");
		menuBar.add(mHelp);

		JMenuItem miAbout = new JMenuItem("About");
		mHelp.add(miAbout);

		this.setJMenuBar(menuBar);

		miNew.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) { fileNew(); };
		});
		miOpen.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) { fileOpen(); };
		});
		miSave.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) { fileSave(); };
		});
		miRun.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) { fileRun(); };
		});
		miExit.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) { fileExit(); };
		});
		miAbout.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) { helpAbout(); };
		});
	}

	public void fileNew() {
		mainPanel.clear();
	}

	public void fileOpen() {
		GUIData data = new GUIData();
		int returnval = fileChooser.showOpenDialog(this);
		if (returnval == JFileChooser.APPROVE_OPTION) {
			try {
				data.read(fileChooser.getSelectedFile());
				mainPanel.clear();
				mainPanel.loadDataIntoGUI(data);
			} catch (IOException e) {
				JOptionPane.showMessageDialog(null,"Unable to open file.");
			}
		}
	}

	public void fileSave() {
		int returnval = fileChooser.showSaveDialog(this);
		if (returnval == JFileChooser.APPROVE_OPTION) {
			GUIData data = new GUIData();
			mainPanel.getDataFromGUI(data);
			try {
				data.write(fileChooser.getSelectedFile());
			} catch (IOException e) {
				JOptionPane.showMessageDialog(null,"Unable to save file.");
			}
		}
	}

	public void fileRun() {
		GUIData data = new GUIData();
		mainPanel.getDataFromGUI(data);

		//Run sims
		for (int i=0;i<data.simulations.size();i++){
			//Declare model
			// 			Model model = new Model(data,i,false);		
			// 			model.start();//GO
		}		
	}

	public void fileExit() {
		System.out.println("TODO: Implement");
		System.exit(1);
	}

	public void helpAbout() {
		System.out.println("TODO: Implement");
	}

	public static void main(String args[]) {
		GUI t = new GUI();
		//		System.out.println(System.getProperty("java.library.path"));
	}
}
